package front

import (
	"bufio"
	"circuit/kit/sched/limiter"
	"errors"
	"fmt"
	"io"
	"net"
	"strconv"
	"strings"
	"vena"
	"vena/server"
)

type Replier interface {
	Put(vena.Time, string, map[string]string, float64)
	Query(string, vena.Time, vena.Time, map[string]string) server.Series
	Dump() interface{}
	DieDieDie()
}

func listenTSDB(addr string, reply Replier) {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	// Accept incoming requests
	go func() {
		lmtr := limiter.New(100) // At most 100 concurrent connections
		for {
			lmtr.Open()
			conn, err := l.Accept()
			if err != nil {
				panic(err)
			}
			// Serve individual connection
			go func() {
				defer lmtr.Close()
				defer conn.Close()
				defer recover() // Recover from panics in reply logic
				// Read request, send reply
				r := bufio.NewReader(conn)
				for {
					line, err := r.ReadString('\n')
					if err != nil {
						println("read line", err.Error())
						break
					}
					cmd, err := parse(strings.TrimSpace(line))
					if err != nil {
						println("parse", err.Error())
						break
					}
					if cmd == nil {
						continue
					}
					switch p := cmd.(type) {
					case diediedie:
						reply.DieDieDie()
					case *put:
						reply.Put(p.Time, p.Metric, p.Tags, p.Value)
					case dump:
						if result := reply.Dump(); result == nil {
							conn.Write([]byte("error\n"))
						} else {
							encodeMultiPut(conn, result.([]*server.Point))
						}
					case *query:
						if result := reply.Query(p.Metric, p.MinTime, p.MaxTime, p.Tags); result == nil {
							conn.Write([]byte("error\n"))
						} else {
							encodeSeries(conn, result)
						}
					}
				}
			}()
		}
	}()
}

func encodeSketch(w io.Writer, s *server.Sketch) error {
	_, err := fmt.Fprintf(w, "%010d % .2f %3.0f", s.Time, s.Sum, s.Weight)
	return err
}

func encodeSeries(w io.Writer, ss server.Series) error {
	for _, s := range ss {
		if err := encodeSketch(w, s); err != nil {
			return err
		}
		if _, err := w.Write([]byte("\n")); err != nil {
			return err
		}
	}
	return nil
}

func encodePut(w io.Writer, p *server.Point) error {
	fmt.Fprintf(w, "PUT ·%08x %010d %f ", p.Metric, p.Time, p.Value)
	for _, t := range p.TagSet {
		if _, err := fmt.Fprintf(w, "·%04x=·%04x ", t.Key, t.Value); err != nil {
			return err
		}
	}
	return nil
}

func encodeMultiPut(w io.Writer, pp []*server.Point) error {
	for _, p := range pp {
		if err := encodePut(w, p); err != nil {
			return err
		}
		if _, err := w.Write([]byte("\n")); err != nil {
			return err
		}
	}
	return nil
}

type diediedie struct{}

type put struct {
	Time   vena.Time
	Metric string
	Tags   map[string]string
	Value  float64
}

type dump struct{}

type query struct {
	Metric  string
	MinTime vena.Time
	MaxTime vena.Time
	Tags    map[string]string
}

func parse(l string) (interface{}, error) {
	t := strings.Split(l, " ")
	if len(t) == 0 {
		return nil, nil
	}
	switch t[0] {
	case "":
		return nil, nil
	case "diediedie":
		return diediedie{}, nil
	case "put":
		t = t[1:]
		if len(t) < 3 {
			return nil, errors.New("too few")
		}
		a := &put{Metric: t[0]}
		// Time
		sec, err := strconv.Atoi(t[1])
		if err != nil {
			return nil, err
		}
		a.Time = vena.Time(sec)
		// Value
		a.Value, err = strconv.ParseFloat(t[2], 64)
		if err != nil {
			return nil, err
		}
		t = t[3:]
		// Tags
		a.Tags = make(map[string]string)
		for _, tv := range t {
			q := strings.SplitN(tv, "=", 2)
			if len(q) != 2 {
				return nil, errors.New("parse tag")
			}
			a.Tags[q[0]] = q[1]
		}
		return a, nil
	case "dump":
		return dump{}, nil
	case "query":
		t = t[1:]
		if len(t) < 3 {
			return nil, errors.New("too few")
		}
		a := &query{Metric: t[0]}
		// MinTime
		minTime, err := strconv.Atoi(t[1])
		if err != nil {
			return nil, err
		}
		a.MinTime = vena.Time(minTime)
		// MaxTime
		maxTime, err := strconv.Atoi(t[2])
		if err != nil {
			return nil, err
		}
		a.MaxTime = vena.Time(maxTime)
		t = t[3:]
		// Tags
		a.Tags = make(map[string]string)
		for _, tv := range t {
			q := strings.SplitN(tv, "=", 2)
			if len(q) != 2 {
				return nil, errors.New("parse tag")
			}
			a.Tags[q[0]] = q[1]
		}
		return a, nil
	}
	return nil, errors.New("unrecognized command")
}
